/* @flow */

import {_Vue} from '../install'
import type Router from '../index'
import {inBrowser} from '../util/dom'
import {runQueue} from '../util/async'
import {warn, isError} from '../util/warn'
import {START, isSameRoute} from '../util/route'
import {
    flatten,
    flatMapComponents,
    resolveAsyncComponents
} from '../util/resolve-components'

export class History {
    router: Router;
    //该属性用于存储通过options.base或者<base href="">
    //设置的base的值，如果未设置，则默认为'/'，该属性的字符串形式必须满足如下条件：
    //1）必须以'/'开头
    //2）不以'/'结尾
    base: string;
    current: Route;
    //@todo 感觉pending的作用与导航守卫有关，用于判断是否要停止导航
    pending: ?Route;
    //listen方法添加的回调函数，该回调会在路由更新后被调用
    cb: (r: Route) => void;
    //表示路由是否完成初始导航
    ready: boolean;
    //正确完成初始导航后的回调函数的数组
    readyCbs: Array<Function>;
    //错误完成初始导航后的回调函数的数组
    readyErrorCbs: Array<Function>;
    //在路由导航过程中出错时被调用的回调函数的数组
    errorCbs: Array<Function>;

    // implemented by sub-classes
    +go: (n: number) => void;
    +push: (loc: RawLocation) => void;
    +replace: (loc: RawLocation) => void;
    //该方法用于根据当前浏览器中的url与this.current中的路径进行比较，
    //如果存在差异，则会新增或者替换历史记录
    +ensureURL: (push?: boolean) => void;
    +getCurrentLocation: () => string;

    constructor(router: Router, base: ?string) {
        this.router = router
        this.base = normalizeBase(base)
        // start with a route object that stands for "nowhere"
        //初始化时的链接状态，也就是'/'状态
        this.current = START
        this.pending = null
        this.ready = false
        this.readyCbs = []
        this.readyErrorCbs = []
        this.errorCbs = []
    }

    //该方法用于添加一个回调函数，该回调函数会在路由更新后被调用，并将最新的路由以参数的形式
    //传递给该函数
    listen(cb: Function) {
        this.cb = cb
    }

    onReady(cb: Function, errorCb: ?Function) {
        if (this.ready) {
            cb()
        } else {
            this.readyCbs.push(cb)
            if (errorCb) {
                this.readyErrorCbs.push(errorCb)
            }
        }
    }

    onError(errorCb: Function) {
        this.errorCbs.push(errorCb)
    }

    transitionTo(location: RawLocation, onComplete?: Function, onAbort?: Function) {
        const route = this.router.match(location, this.current)
        this.confirmTransition(route, () => {
            this.updateRoute(route)
            onComplete && onComplete(route)
            //在成功完成后新增或者替换历史记录
            this.ensureURL()

            // fire ready cbs once
            if (!this.ready) {
                this.ready = true
                this.readyCbs.forEach(cb => {
                    cb(route)
                })
            }
        }, err => {
            if (onAbort) {
                onAbort(err)
            }
            if (err && !this.ready) {
                this.ready = true
                this.readyErrorCbs.forEach(cb => {
                    cb(err)
                })
            }
        })
    }

    confirmTransition(route: Route, onComplete: Function, onAbort?: Function) {
        const current = this.current
        const abort = err => {
            if (isError(err)) {
                if (this.errorCbs.length) {
                    this.errorCbs.forEach(cb => {
                        cb(err)
                    })
                } else {
                    warn(false, 'uncaught error during route navigation:')
                    console.error(err)
                }
            }
            onAbort && onAbort(err)
        }
        if (
            isSameRoute(route, current) &&
            // in the case the route map has been dynamically appended to
            route.matched.length === current.matched.length
        ) {
            this.ensureURL()
            return abort()
        }

        const {
            updated,
            deactivated,
            activated
        } = resolveQueue(this.current.matched, route.matched)

        //这里导航守卫的执行过程同vue-router文档中的说明完全相同
        //1.导航被触发。
        const queue: Array<?NavigationGuard> = [].concat(
            // in-component leave guards
            //2.在失活的组件里调用离开守卫。
            extractLeaveGuards(deactivated),
            // global before hooks
            //3.调用全局的 beforeEach 守卫。
            this.router.beforeHooks,
            // in-component update hooks
            //4.在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
            extractUpdateHooks(updated),
            // in-config enter guards
            //5.在路由配置里调用 beforeEnter。
            activated.map(m => m.beforeEnter),
            // async components
            //@todo 暂不研究异步组件
            //6.解析异步路由组件。
            resolveAsyncComponents(activated)
        )

        this.pending = route
        const iterator = (hook: NavigationGuard, next) => {
            //@todo 为什么这么处理？
            if (this.pending !== route) {
                return abort()
            }
            try {
                hook(route, current, (to: any) => {
                    if (to === false || isError(to)) {
                        // next(false) -> abort navigation, ensure current URL
                        this.ensureURL(true)
                        abort(to)
                    } else if (
                        typeof to === 'string' ||
                        (typeof to === 'object' && (
                            typeof to.path === 'string' ||
                            typeof to.name === 'string'
                        ))
                    ) {
                        // next('/') or next({ path: '/' }) -> redirect
                        abort()
                        if (typeof to === 'object' && to.replace) {
                            this.replace(to)
                        } else {
                            this.push(to)
                        }
                    } else {
                        // confirm transition and pass on the value
                        next(to)
                    }
                })
            } catch (e) {
                abort(e)
            }
        }

        runQueue(queue, iterator, () => { //该回调函数会queue中的所有导航守卫执行完成后运行
            const postEnterCbs = []
            const isValid = () => this.current === route
            // wait until async components are resolved before
            // extracting in-component enter guards
            //7.在被激活的组件里调用 beforeRouteEnter。
            const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
            //8.调用全局的 beforeResolve 守卫 (2.5+)。
            const queue = enterGuards.concat(this.router.resolveHooks)
            runQueue(queue, iterator, () => {
                if (this.pending !== route) {
                    return abort()
                }
                this.pending = null
                onComplete(route)
                if (this.router.app) {
                    this.router.app.$nextTick(() => {
                        //12.用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
                        postEnterCbs.forEach(cb => {
                            cb()
                        })
                    })
                }
            })
        })
    }

    updateRoute(route: Route) {
        const prev = this.current
        //9.导航被确认。
        this.current = route
        this.cb && this.cb(route)
        //10.调用全局的 afterEach 钩子。
        this.router.afterHooks.forEach(hook => {
            hook && hook(route, prev)
        })
    }
}

function normalizeBase(base: ?string): string {
    if (!base) {
        if (inBrowser) {
            // respect <base> tag
            const baseEl = document.querySelector('base')
            base = (baseEl && baseEl.getAttribute('href')) || '/'
            // strip full URL origin
            //将'https://www.baidu.com/hello'形式的字符串转换为'/hello'形式的字符串
            base = base.replace(/^https?:\/\/[^\/]+/, '')
        } else {
            base = '/'
        }
    }
    // make sure there's the starting slash
    if (base.charAt(0) !== '/') {
        base = '/' + base
    }
    // remove trailing slash
    return base.replace(/\/$/, '')
}

function resolveQueue(current: Array<RouteRecord>,
                      next: Array<RouteRecord>): {
    updated: Array<RouteRecord>,
    activated: Array<RouteRecord>,
    deactivated: Array<RouteRecord>
} {
    let i
    const max = Math.max(current.length, next.length)
    for (i = 0; i < max; i++) {
        if (current[i] !== next[i]) {
            break
        }
    }

    //示例：
    //如果next为：[1], i为10，则next.slice(10)为[1]
    return {
        updated: next.slice(0, i),
        activated: next.slice(i),
        deactivated: current.slice(i)
    }
}

function extractGuards(records: Array<RouteRecord>,
                       name: string,
                       bind: Function,
                       reverse?: boolean): Array<?Function> {
    //(def, instance, match, key)分别是：
    //def：vue组件的定义，可能为vue组件的构造函数或者vue组件的选项对象
    //instance：与RouteRecord关联的vue实例
    //match：RouteRecord对象本身
    //key：RouteRecord中的components属性中的键
    const guards = flatMapComponents(records, (def, instance, match, key) => {
        const guard = extractGuard(def, name)
        if (guard) {
            return Array.isArray(guard)
                ? guard.map(guard => bind(guard, instance, match, key))
                : bind(guard, instance, match, key)
        }
    })
    return flatten(reverse ? guards.reverse() : guards)
}

function extractGuard(def: Object | Function,
                      key: string): NavigationGuard | Array<NavigationGuard> {
    if (typeof def !== 'function') {
        // extend now so that global mixins are applied.
        def = _Vue.extend(def)
    }
    return def.options[key]
}

function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
    return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}

function extractUpdateHooks(updated: Array<RouteRecord>): Array<?Function> {
    return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
}

function bindGuard(guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
    if (instance) {
        return function boundRouteGuard() {
            return guard.apply(instance, arguments)
        }
    }
}

function extractEnterGuards(activated: Array<RouteRecord>,
                            cbs: Array<Function>,
                            isValid: () => boolean): Array<?Function> {
    return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {
        return bindEnterGuard(guard, match, key, cbs, isValid)
    })
}

function bindEnterGuard(guard: NavigationGuard,
                        match: RouteRecord,
                        key: string,
                        cbs: Array<Function>,
                        isValid: () => boolean): NavigationGuard {
    return function routeEnterGuard(to, from, next) {
        return guard(to, from, cb => {
            //这里的含义就是运行下一个导航守卫，要明白传递cb的原因，那就首先需要知道next函数是哪个函数，
            //next是confirmTransition方法中的iterator函数中运行的hook函数的第三个参数，所以如果cb确实为函数，
            //则会运行next函数（也就是hook函数的第三个参数）中的else语句，所以最终会运行下一个导航守卫，
            //当然，cb的类型也可以是false、Error对象、字符串或者对象，cb的类型与beforeRouteUpdate的next方法可设置的类型
            //相同，只是可以多设置一个函数类型。
            next(cb)
            if (typeof cb === 'function') {
                cbs.push(() => {
                    // #750
                    // if a router-view is wrapped with an out-in transition,
                    // the instance may not have been registered at this time.
                    // we will need to poll for registration until current route
                    // is no longer valid.
                    poll(cb, match.instances, key, isValid)
                })
            }
        })
    }
}

function poll(cb: any, // somehow flow cannot infer this is a function
              instances: Object,
              key: string,
              isValid: () => boolean) {
    if (instances[key]) {
        cb(instances[key])
    } else if (isValid()) {
        setTimeout(() => {
            poll(cb, instances, key, isValid)
        }, 16)
    }
}
